﻿#region << 版 本 注 释 >>
/*----------------------------------------------------------------
 * 版权所有 (c) 2022 北京超维景生物科技有限公司 保留所有权利。
 * CLR版本：4.0.30319.42000
 * 文件名：Kernel
 * 
 * 创建者：huangyang
 * 电子邮箱：huangyang@tvscope.cn
 * 创建时间：2023/2/9 13:51:30
 * 版本：V1.0.0
 * 描述：
 *
 * ----------------------------------------------------------------
 * 修改人：
 * 时间：
 * 修改说明：
 *
 * 版本：V1.0.1
 *----------------------------------------------------------------*/
#endregion << 版 本 注 释 >>

using System;
using System.Collections.Generic;
using System.Drawing.Imaging;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using ImageK.Java;
using ImageK.Util;

namespace ImageK.Process
{
    public class ShortProcessor : ImageProcessor
    {
        private int _min, _max, snapshotMin, snapshotMax;
        private short[] _pixels;
        private byte[] _pixels8;
        private short[] _snapshotPixels;
        private byte[] LUT;
        private bool fixedScale;
        private int bgValue;

        /** Creates a new ShortProcessor using the specified pixel array and ColorModel.
	    Set 'cm' to null to use the default grayscale LUT. */
        public ShortProcessor(int width, int height, short[] pixels, ColorModel cm)
        {
            if (pixels != null && width * height != pixels.Length)
                throw new ArgumentException(WRONG_LENGTH);
            init(width, height, pixels, cm);
        }

        /** Creates a blank ShortProcessor using the default grayscale LUT that
        displays zero as black. Call invertLut() to display zero as white. */
        public ShortProcessor(int width, int height): this(width, height, new short[width*height], null)
        {
            
        }

        /** Creates a ShortProcessor from a TYPE_USHORT_GRAY BufferedImage. */
        public ShortProcessor(Bitmap bi)
        {
            if (bi.PixelFormat != System.Drawing.Imaging.PixelFormat.Format16bppGrayScale)
                throw new ArgumentException("Type!=TYPE_USHORT_GRAY");
            BitmapData bmpData = bi.LockBits(new Rectangle(0, 0, bi.Width, bi.Height), ImageLockMode.ReadOnly, bi.PixelFormat);
            IntPtr ptr = bmpData.Scan0;
            short[] data = new short[bi.Width * bi.Height];
            Marshal.Copy(ptr, data, 0, bi.Width * bi.Height);
            bi.UnlockBits(bmpData);
            init(bi.Width, bi.Height, data, null);
        }

        void init(int width, int height, short[] pixels, ColorModel cm)
        {
            this.width = width;
            this.height = height;
            this._pixels = pixels;
            this.cm = cm;
            resetRoi();
        }

        /** Returns the smallest displayed pixel value. */
        public override double getMin()
        {
            if (!MinMaxSet) findMinAndMax();
            return _min;
        }

        /** Returns the largest displayed pixel value. */
        public override double getMax()
        {
            if (!MinMaxSet) findMinAndMax();
            return _max;
        }

        public void findMinAndMax()
        {
            if (fixedScale || _pixels == null)
                return;
            int size = width * height;
            int value;
            int min = _pixels[0] & 0xffff;
            int max = _pixels[0] & 0xffff;
            for (int i = 1; i < size; i++)
            {
                value = _pixels[i] & 0xffff;
                if (value < min)
                    min = value;
                else if (value > max)
                    max = value;
            }

            this._min = min;
            this._max = max;
            MinMaxSet = true;
        }



        /// <summary>
        /// Create an 8-bit AWT image by scaling pixels in the range min-max to 0-255.
        /// 将16bit图像映射为8bit图像，便于显示器界面展示
        /// </summary>
        /// <returns></returns>
        public override Image createImage()
        {
            if (!MinMaxSet)
                findMinAndMax();
            bool firstTime = _pixels8 == null;
            bool thresholding = minThreshold != NO_THRESHOLD && lutUpdateMode < NO_LUT_UPDATE;
            //ij.IJ.log("createImage: "+firstTime+"  "+lutAnimation+"  "+thresholding);
            if (firstTime || !lutAnimation)
                create8BitImage(thresholding && lutUpdateMode == RED_LUT);
            if (cm == null)
                makeDefaultColorModel();
            if (thresholding)
            {
                int t1 = (int)minThreshold;
                int t2 = (int)maxThreshold;
                int size = width * height;
                int value;
                if (lutUpdateMode == BLACK_AND_WHITE_LUT)
                {
                    for (int i = 0; i < size; i++)
                    {
                        value = (_pixels[i] & 0xffff);
                        if (value >= t1 && value <= t2)
                            _pixels8[i] = (byte)255;
                        else
                            _pixels8[i] = (byte)0;
                    }
                }
                else
                {
                    // threshold red
                    for (int i = 0; i < size; i++)
                    {
                        value = (_pixels[i] & 0xffff);
                        if (value >= t1 && value <= t2)
                            _pixels8[i] = (byte)255;
                    }
                }
            }

            return createBufferedImage();
        }

        public override void snapshot()
        {
            snapshotWidth = width;
            snapshotHeight = height;
            snapshotMin = (int)getMin();
            snapshotMax = (int)getMax();
            if (_snapshotPixels == null || (_snapshotPixels != null && _snapshotPixels.Length != _pixels.Length))
            {
                _snapshotPixels = new short[width * height];
            }

            Array.Copy(_pixels, 0, _snapshotPixels, 0, width * height);
        }

        /** Returns the LUT index that's the best match for this color. */
        public int getBestIndex(Color c)
        {
            IndexColorModel icm;
            if (cm == null)
                makeDefaultColorModel();
            if (minThreshold != NO_THRESHOLD)
            {
                double saveMin = getMinThreshold();
                double saveMax = getMaxThreshold();
                resetThreshold();
                icm = (IndexColorModel)cm;
                setThreshold(saveMin, saveMax, lutUpdateMode);
            }
            else
                icm = (IndexColorModel)cm;

            int mapSize = icm.getMapSize();
            byte[] rLUT = new byte[mapSize];
            byte[] gLUT = new byte[mapSize];
            byte[] bLUT = new byte[mapSize];
            icm.getReds(rLUT);
            icm.getGreens(gLUT);
            icm.getBlues(bLUT);
            int minDistance = int.MaxValue;
            int distance;
            int minIndex = 0;
            int r1 = c.R;
            int g1 = c.G;
            int b1 = c.B;
            if (!(r1 == g1 && g1 == b1 && r1 == b1) && icm == defaultColorModel)
            {
                double[] w = ColorProcessor.getWeightingFactors();
                r1 = (int)Math.Round(3 * r1 * w[0]);
                g1 = (int)Math.Round(3 * g1 * w[1]);
                b1 = (int)Math.Round(3 * b1 * w[2]);
            }

            int r2, b2, g2;
            for (int i = 0; i < mapSize; i++)
            {
                r2 = rLUT[i] & 0xff;
                g2 = gLUT[i] & 0xff;
                b2 = bLUT[i] & 0xff;
                distance = (r2 - r1) * (r2 - r1) + (g2 - g1) * (g2 - g1) + (b2 - b1) * (b2 - b1);
                //ij.IJ.log("  "+i+" "+minIndex+" "+distance+"   "+(rLUT[i]&255)+" "+(gLUT[i]&255)+" "+(bLUT[i]&255));
                if (distance < minDistance)
                {
                    minDistance = distance;
                    minIndex = i;
                }

                if (minDistance == 0.0)
                    break;
            }

            return minIndex;
        }

        public override void reset()
        {
            if (_snapshotPixels == null)
                return;
            _min = snapshotMin;
            _max = snapshotMax;
            MinMaxSet = true;

            Array.Copy(_snapshotPixels, 0, _pixels, 0, width * height);
        }

        public override void reset(ImageProcessor mask)
        {
            if (mask == null || _snapshotPixels == null)
                return;
            if (mask.getWidth() != roiWidth || mask.getHeight() != roiHeight)
                throw new ArgumentException(maskSizeError(mask));
            byte[] mpixels = (byte[])mask.getPixels();
            for (int y = roiY, my = 0; y < (roiY + roiHeight); y++, my++)
            {
                int i = y * width + roiX;
                int mi = my * roiWidth;
                for (int x = roiX; x < (roiX + roiWidth); x++)
                {
                    if (mpixels[mi++] == 0)
                        _pixels[i] = _snapshotPixels[i];
                    i++;
                }
            }
        }

        /// <summary>
        /// Swaps the pixel and snapshot (undo) arrays.
        /// </summary>
        public override void swapPixelArrays()
        {
            if (_snapshotPixels == null) return;
            short pixel;
            for (int i = 0; i < _pixels.Length; i++)
            {
                pixel = _pixels[i];
                _pixels[i] = _snapshotPixels[i];
                _snapshotPixels[i] = pixel;
            }
        }



        /// <summary>
        /// create 8-bit image by linearly scaling from 16-bits to 8-bits
        /// </summary>
        /// <param name="thresholding"></param>
        /// <returns></returns>
        private byte[] create8BitImage(bool thresholding)
        {
            int size = width * height;
            if (_pixels8 == null)
                _pixels8 = new byte[size];
            int value;
            int min2 = (int)getMin(), max2 = (int)getMax();
            int maxValue = 255;
            double scale = 256.0 / (max2 - min2 + 1);
            if (thresholding)
            {
                maxValue = 254;
                scale = 255.0 / (max2 - min2 + 1);
            }

            for (int i = 0; i < size; i++)
            {
                value = (_pixels[i] & 0xffff) - min2;
                if (value < 0) value = 0;
                value = (int)(value * scale + 0.5);
                if (value > maxValue) value = maxValue;
                _pixels8[i] = (byte)value;
            }

            return _pixels8;
        }

        Image createBufferedImage()
        {
            if (raster == null)
            {
                //todo:
                // SampleModel sm = getIndexSampleModel();
                // DataBuffer db = new DataBufferByte(pixels8, width * height, 0);
                // raster = Raster.createWritableRaster(sm, db, null);
            }

            // if (image == null || cm != cm2)
            {
                if (cm == null) cm = getDefaultColorModel();
                //todo:
                image = ImageUtil.BuildByBytes(_pixels8, width, height, cm);
                // image = new BufferedImage(cm, raster, false, null);
                cm2 = cm;
            }
            lutAnimation = false;
            return image;
        }

        /** Returns a new, blank ShortProcessor with the specified width and height. */
        public override ImageProcessor createProcessor(int width, int height)
        {
            ImageProcessor ip2 = new ShortProcessor(width, height, new short[width * height], getColorModel());
            ip2.setMinAndMax(getMin(), getMax());
            ip2.setInterpolationMethod(interpolationMethod);
            return ip2;
        }

        public override double getBackgroundValue()
        {
            throw new NotImplementedException();
        }

        public override void setPixels(object pixels)
        {
            this._pixels = (short[])pixels;
            resetPixels(pixels);
            if (pixels == null) _snapshotPixels = null;
            if (pixels == null) _pixels8 = null;
            raster = null;
        }

        /** Copies the image contained in 'ip' to (xloc, yloc) using one of
            the transfer modes defined in the Blitter interface. */
        public override void copyBits(ImageProcessor ip, int xloc, int yloc, int mode)
        {
            bool temporaryFloat = ip.getBitDepth() == 32 && (mode == Blitter.MULTIPLY || mode == Blitter.DIVIDE);
            if (temporaryFloat)
            {
                // FloatProcessor ipFloat = this.convertToFloatProcessor();
                // new FloatBlitter(ipFloat).copyBits(ip, xloc, yloc, mode);
                // setPixels(1, ipFloat);
            }
            else
            {
                // ip = ip.convertToShort(false);
                // new ShortBlitter(this).copyBits(ip, xloc, yloc, mode);
            }
        }

        /**
        Sets the min and max variables that control how real
        pixel values are mapped to 0-255 screen values. With
        signed 16-bit images, use IJ.setMinAndMax(imp,min,max).
        @see #resetMinAndMax
        @see ij.plugin.frame.ContrastAdjuster 
        @see ij.IJ#setMinAndMax(ij.ImagePlus,double,double)
        */
        public override void setMinAndMax(double minimum, double maximum)
        {
            if (minimum == 0.0 && maximum == 0.0)
            {
                resetMinAndMax();
                return;
            }

            if (minimum < 0.0)
                minimum = 0.0;
            if (maximum > 65535.0)
                maximum = 65535.0;
            _min = (int)minimum;
            _max = (int)maximum;
            fixedScale = true;
            MinMaxSet = true;
            resetThreshold();
        }

        /** Recalculates the min and max values used to scale pixel
            values to 0-255 for display. This ensures that this 
            ShortProcessor is set up to correctly display the image. */
        public override void resetMinAndMax()
        {
            fixedScale = false;
            findMinAndMax();
            resetThreshold();
        }

        /**	Returns a reference to the short array containing this image's
            pixel data. To avoid sign extension, the pixel values must be
            accessed using a mask (e.g. int i = pixels[j]&0xffff). */
        public override object getPixels()
        {
            return (object)_pixels;
        }

        /** Returns a copy of the pixel data. Or returns a reference to the
            snapshot buffer if it is not null and 'snapshotCopyMode' is true.
            @see ImageProcessor#snapshot
            @see ImageProcessor#setSnapshotCopyMode
        */
        public override object getPixelsCopy()
        {
            if (_snapshotPixels != null && snapshotCopyMode)
            {
                snapshotCopyMode = false;
                return _snapshotPixels;
            }
            else
            {
                short[] pixels2 = new short[width * height];
                Array.Copy(_pixels, 0, pixels2, 0, width * height);
                return pixels2;
            }
        }

        public override void setSnapshotPixels(object pixels)
        {
            _snapshotPixels = (short[])pixels;
            snapshotWidth = width;
            snapshotHeight = height;
        }

        public override object getSnapshotPixels()
        {
            return _snapshotPixels;
        }

        /** Returns the value of the pixel at (x,y) as a float. For signed
            images, returns a signed value if a calibration table has
            been set using setCalibrationTable() (this is done automatically 
            in PlugInFilters). */
        public override float getPixelValue(int x, int y)
        {
            if (x >= 0 && x < width && y >= 0 && y < height)
            {
                if (cTable == null)
                    return _pixels[y * width + x] & 0xffff;
                else
                    return cTable[_pixels[y * width + x] & 0xffff];
            }
            else
                return float.NaN;
        }

        /** Stores the specified real value at (x,y). Does nothing
            if (x,y) is outside the image boundary. Values outside 
            the range 0-65535 (-32768-32767 for signed images)
            are clipped. Support for signed values requires a calibration
            table, which is set up automatically with PlugInFilters.
        */
        public override void putPixelValue(int x, int y, double value)
        {
            if (x >= 0 && x < width && y >= 0 && y < height)
            {
                if (cTable != null && cTable[0] == -32768f) // signed image
                    value += 32768.0;
                if (value > 65535.0)
                    value = 65535.0;
                else if (value < 0.0)
                    value = 0.0;
                _pixels[y * width + x] = (short)(value + 0.5);
            }
        }

        /** Draws a pixel in the current foreground color. */
        public override void drawPixel(int x, int y)
        {
            if (x >= clipXMin && x <= clipXMax && y >= clipYMin && y <= clipYMax)
                putPixel(x, y, fgColor);
        }

        /** Sets the foreground fill/draw color. */
        public override void setColor(Color color)
        {
            drawingColor = color;
            int bestIndex = getBestIndex(color);
            if (bestIndex > 0 && getMin() == 0.0 && getMax() == 0.0)
            {
                setValue(bestIndex);
                setMinAndMax(0.0, 255.0);
            }
            else if (bestIndex == 0 && getMin() > 0.0 && (color.ToArgb() & 0xffffff) == 0)
            {
                if (isSigned16Bit())
                    setValue(32768);
                else
                    setValue(0.0);
            }
            else
                fgColor = (int)(getMin() + (getMax() - getMin()) * (bestIndex / 255.0));

            _fillValueSet = true;
        }

        /** Sets the background fill/draw color. */
        public void setBackgroundColor(Color color)
        {
            int bestIndex = getBestIndex(color);
            int value = (int)(getMin() + (getMax() - getMin()) * (bestIndex / 255.0));
            setBackgroundValue(value);
        }

        /** Sets the default fill/draw value, where 0<=value<=65535). */
        public override void setValue(double value)
        {
            fgColor = (int)value;
            if (fgColor < 0) fgColor = 0;
            if (fgColor > 65535) fgColor = 65535;
            _fillValueSet = true;
        }

        public override void setBackgroundValue(double value)
        {
            bgValue = (int)value;
            if (bgValue < 0) bgValue = 0;
            if (bgValue > 65535) bgValue = 65535;
        }

        /** Returns a FloatProcessor with the same image, no scaling or calibration
        *  (pixel values 0 to 65535).
        *  The roi, mask, lut (ColorModel), threshold, min&max are
        *  also set for the FloatProcessor
        *  @param channelNumber   Ignored (needed for compatibility with ColorProcessor.toFloat)
        *  @param fp              Here a FloatProcessor can be supplied, or null. The FloatProcessor
        *                         is overwritten by this method (re-using its pixels array 
        *                         improves performance).
        *  @return A FloatProcessor with the converted image data
        */
        public override FloatProcessor toFloat(int channelNumber, FloatProcessor fp)
        {
            int size = width * height;
            if (fp == null || fp.getWidth() != width || fp.getHeight() != height)
                fp = new FloatProcessor(width, height, new float[size], cm);
            float[] fPixels = (float[])fp.getPixels();
            for (int i = 0; i < size; i++)
                fPixels[i] = _pixels[i] & 0xffff;
            fp.setRoi(getRoi());
            fp.setMask(mask);
            fp.setMinAndMax(getMin(), getMax());
            fp.setThreshold(minThreshold, maxThreshold, ImageProcessor.NO_LUT_UPDATE);
            return fp;
        }

        /** Sets the pixels from a FloatProcessor, no scaling.
        *  Also the min&max values are taken from the FloatProcessor.
        *  @param channelNumber   Ignored (needed for compatibility with ColorProcessor.toFloat)
        *  @param fp              The FloatProcessor where the image data are read from.
        */
        public override void setPixels(int channelNumber, FloatProcessor fp)
        {
            float[] fPixels = (float[])fp.getPixels();
            float value;
            int size = width * height;
            for (int i = 0; i < size; i++)
            {
                value = fPixels[i] + 0.5f;
                if (value < 0f) value = 0f;
                if (value > 65535f) value = 65535f;
                _pixels[i] = (short)value;
            }
            setMinAndMax(fp.getMin(), fp.getMax());
        }

        public override int getPixel(int x, int y)
        {
            if (x >= 0 && x < width && y >= 0 && y < height)
                return _pixels[y * width + x] & 0xffff;
            else
                return 0;
        }

        /** Uses the current interpolation method (BILINEAR or BICUBIC)
        to calculate the pixel value at real coordinates (x,y). */
        public override double getInterpolatedPixel(double x, double y)
        {
            if (interpolationMethod == BICUBIC)
                return getBicubicInterpolatedPixel(x, y, this);
            else
            {
                if (x < 0.0) x = 0.0;
                if (x >= width - 1.0) x = width - 1.001;
                if (y < 0.0) y = 0.0;
                if (y >= height - 1.0) y = height - 1.001;
                return getInterpolatedPixel(x, y, _pixels);
            }
        }

        public override int getPixelInterpolated(double x, double y)
        {
            if (interpolationMethod==BILINEAR)
            {
                if (x<0.0 || y<0.0 || x>=width-1 || y>=height-1)
                    return 0;
                else
                    return (int)Math.Round(getInterpolatedPixel(x, y, _pixels));
            }
            else if (interpolationMethod==BICUBIC)
            {
                int value = (int)(getBicubicInterpolatedPixel(x, y, this)+0.5);
                if (value<0) value = 0;
                if (value>65535) value = 65535;
                return value;
            }
            else
                return getPixel((int)(x+0.5), (int)(y+0.5));
        }

        /** Stores the specified value at (x,y). Does
            nothing if (x,y) is outside the image boundary.
            Values outside the range 0-65535 are clipped.
        */
        public override void putPixel(int x, int y, int value)
        {
            if (x >= 0 && x < width && y >= 0 && y < height)
            {
                if (value > 65535) value = 65535;
                if (value < 0) value = 0;
                _pixels[y * width + x] = (short)value;
            }
        }

        public override int get(int x, int y)
        {
            return _pixels[y * width + x] & 0xffff;
        }

        public override void set(int x, int y, int value)
        {
            _pixels[y * width + x] = (short)value;
        }

        public override int get(int index)
        {
            return _pixels[index] & 0xffff;
        }

        public override void set(int index, int value)
        {
            _pixels[index] = (short)value;
        }

        public override float getf(int x, int y)
        {
            return _pixels[y * width + x] & 0xffff;
        }

        public override void setf(int x, int y, float value)
        {
            _pixels[y * width + x] = (short)(value + 0.5f);
        }

        public override float getf(int index)
        {
            return _pixels[index] & 0xffff;
        }

        public override void setf(int index, float value)
        {
            _pixels[index] = (short)value;
        }

        public override void flipVertical()
        {
            throw new NotImplementedException();
        }

        /** Returns 65,536 bin histogram of the current ROI, which
            can be non-rectangular. */
        public override int[] getHistogram()
        {
            if (mask != null)
                return getHistogram(mask);
            int roiX = this.roiX, roiY = this.roiY;
            int roiWidth = this.roiWidth, roiHeight = this.roiHeight;
            int[] histogram = new int[65536];
            for (int y = roiY; y < (roiY + roiHeight); y++)
            {
                int i = y * width + roiX;
                for (int x = roiX; x < (roiX + roiWidth); x++)
                    histogram[_pixels[i++] & 0xffff]++;
            }

            return histogram;
        }

        public override void threshold(int level)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Returns a duplicate of this image.
        /// </summary>
        /// <returns></returns>
        /// <exception cref="NotImplementedException"></exception>
        public override ImageProcessor duplicate()
        {
            ImageProcessor ip2 = createProcessor(width, height);
            short[] pixels2 = (short[])ip2.getPixels();
            Array.Copy(_pixels, 0, pixels2, 0, width * height);
            return ip2;
        }

        public override void scale(double xScale, double yScale)
        {
            throw new NotImplementedException();
        }

        /** Uses bilinear interpolation to find the pixel value at real coordinates (x,y). */
        private double getInterpolatedPixel(double x, double y, short[] pixels)
        {
            int xbase = (int)x;
            int ybase = (int)y;
            double xFraction = x - xbase;
            double yFraction = y - ybase;
            int offset = ybase * width + xbase;
            int lowerLeft = pixels[offset] & 0xffff;
            int lowerRight = pixels[offset + 1] & 0xffff;
            int upperRight = pixels[offset + width + 1] & 0xffff;
            int upperLeft = pixels[offset + width] & 0xffff;
            double upperAverage = upperLeft + xFraction * (upperRight - upperLeft);
            double lowerAverage = lowerLeft + xFraction * (lowerRight - lowerLeft);
            return lowerAverage + yFraction * (upperAverage - lowerAverage);
        }

        /// <summary>
        /// Creates a new ShortProcessor containing a scaled copy of this image or selection.
        /// </summary>
        /// <param name="dstWidth"></param>
        /// <param name="dstHeight"></param>
        /// <returns></returns>
        /// <exception cref="NotImplementedException"></exception>
        public override ImageProcessor resize(int dstWidth, int dstHeight)
        {
            if (roiWidth == dstWidth && roiHeight == dstHeight)
                return crop();
            if ((width == 1 || height == 1) && interpolationMethod != NONE)
                return resizeLinearly(dstWidth, dstHeight);
            double srcCenterX = roiX + roiWidth / 2.0;
            double srcCenterY = roiY + roiHeight / 2.0;
            double dstCenterX = dstWidth / 2.0;
            double dstCenterY = dstHeight / 2.0;
            double xScale = (double)dstWidth / roiWidth;
            double yScale = (double)dstHeight / roiHeight;
            if (interpolationMethod != NONE)
            {
                if (dstWidth != width) dstCenterX += xScale / 4.0;
                if (dstHeight != height) dstCenterY += yScale / 4.0;
            }

            int inc = getProgressIncrement(dstWidth, dstHeight);
            ImageProcessor ip2 = createProcessor(dstWidth, dstHeight);
            short[] pixels2 = (short[])ip2.getPixels();
            double xs, ys;
            if (interpolationMethod == BICUBIC)
            {
                for (int y = 0; y <= dstHeight - 1; y++)
                {
                    if (inc > 0 && y % inc == 0) showProgress((double)y / dstHeight);
                    ys = (y - dstCenterY) / yScale + srcCenterY;
                    int index2 = y * dstWidth;
                    for (int x = 0; x <= dstWidth - 1; x++)
                    {
                        xs = (x - dstCenterX) / xScale + srcCenterX;
                        int value = (int)(getBicubicInterpolatedPixel(xs, ys, this) + 0.5);
                        if (value < 0) value = 0;
                        if (value > 65535) value = 65535;
                        pixels2[index2++] = (short)value;
                    }
                }
            }
            else
            {
                double xlimit = width - 1.0, xlimit2 = width - 1.001;
                double ylimit = height - 1.0, ylimit2 = height - 1.001;
                int index1, index2;
                for (int y = 0; y <= dstHeight - 1; y++)
                {
                    if (inc > 0 && y % inc == 0) showProgress((double)y / dstHeight);
                    ys = (y - dstCenterY) / yScale + srcCenterY;
                    if (interpolationMethod == BILINEAR)
                    {
                        if (ys < 0.0) ys = 0.0;
                        if (ys >= ylimit) ys = ylimit2;
                    }

                    index1 = width * (int)ys;
                    index2 = y * dstWidth;
                    for (int x = 0; x <= dstWidth - 1; x++)
                    {
                        xs = (x - dstCenterX) / xScale + srcCenterX;
                        if (interpolationMethod == BILINEAR)
                        {
                            if (xs < 0.0) xs = 0.0;
                            if (xs >= xlimit) xs = xlimit2;
                            pixels2[index2++] = (short)(getInterpolatedPixel(xs, ys, _pixels) + 0.5);
                        }
                        else
                            pixels2[index2++] = _pixels[index1 + (int)xs];
                    }
                }
            }

            if (inc > 0) showProgress(1.0);
            return ip2;
        }

        public override void convolve(float[] kernel, int kernelWidth, int kernelHeight)
        {
            throw new NotImplementedException();
        }

        /** Does 3x3 convolution. */
        public override void convolve3x3(int[] kernel)
        {
            filter3x3(CONVOLVE, kernel); throw new NotImplementedException();
        }

        /** Filters using a 3x3 neighborhood. */
        public override void filter(int type)
        {
            filter3x3(type, null);
        }

        /** 3x3 filter operations, code partly based on 3x3 convolution code
        *  contributed by Glynne Casteel. */
        void filter3x3(int type, int[] kernel)
        {
            int v1, v2, v3;           //input pixel values around the current pixel
            int v4, v5, v6;
            int v7, v8, v9;
            int k1 = 0, k2 = 0, k3 = 0;  //kernel values (used for CONVOLVE only)
            int k4 = 0, k5 = 0, k6 = 0;
            int k7 = 0, k8 = 0, k9 = 0;
            int scale = 0;
            if (type == CONVOLVE)
            {
                k1 = kernel[0]; k2 = kernel[1]; k3 = kernel[2];
                k4 = kernel[3]; k5 = kernel[4]; k6 = kernel[5];
                k7 = kernel[6]; k8 = kernel[7]; k9 = kernel[8];
                for (int i = 0; i < kernel.Length; i++)
                    scale += kernel[i];
                if (scale == 0) scale = 1;
            }

            short[] pixels2 = (short[])getPixelsCopy();
            int xEnd = roiX + roiWidth;
            int yEnd = roiY + roiHeight;
            for (int y = roiY; y < yEnd; y++)
            {
                int p = roiX + y * width;            //points to current pixel
                int p6 = p - (roiX > 0 ? 1 : 0);      //will point to v6, currently lower
                int p3 = p6 - (y > 0 ? width : 0);    //will point to v3, currently lower
                int p9 = p6 + (y < height - 1 ? width : 0); // ...  to v9, currently lower
                v2 = pixels2[p3] & 0xffff;
                v5 = pixels2[p6] & 0xffff;
                v8 = pixels2[p9] & 0xffff;
                if (roiX > 0) { p3++; p6++; p9++; }
                v3 = pixels2[p3] & 0xffff;
                v6 = pixels2[p6] & 0xffff;
                v9 = pixels2[p9] & 0xffff;

                switch (type)
                {
                    case BLUR_MORE:
                        for (int x = roiX; x < xEnd; x++, p++)
                        {
                            if (x < width - 1) { p3++; p6++; p9++; }
                            v1 = v2; v2 = v3;
                            v3 = pixels2[p3] & 0xffff;
                            v4 = v5; v5 = v6;
                            v6 = pixels2[p6] & 0xffff;
                            v7 = v8; v8 = v9;
                            v9 = pixels2[p9] & 0xffff;
                            _pixels[p] = (short)((v1 + v2 + v3 + v4 + v5 + v6 + v7 + v8 + v9 + 4) / 9);
                        }
                        break;
                    case FIND_EDGES:
                        for (int x = roiX; x < xEnd; x++, p++)
                        {
                            if (x < width - 1) { p3++; p6++; p9++; }
                            v1 = v2; v2 = v3;
                            v3 = pixels2[p3] & 0xffff;
                            v4 = v5; v5 = v6;
                            v6 = pixels2[p6] & 0xffff;
                            v7 = v8; v8 = v9;
                            v9 = pixels2[p9] & 0xffff;
                            double sum1 = v1 + 2 * v2 + v3 - v7 - 2 * v8 - v9;
                            double sum2 = v1 + 2 * v4 + v7 - v3 - 2 * v6 - v9;
                            double result = Math.Sqrt(sum1 * sum1 + sum2 * sum2);
                            if (result > 65535.0) result = 65535.0;
                            _pixels[p] = (short)result;
                        }
                        break;
                    case CONVOLVE:
                        for (int x = roiX; x < xEnd; x++, p++)
                        {
                            if (x < width - 1) { p3++; p6++; p9++; }
                            v1 = v2; v2 = v3;
                            v3 = pixels2[p3] & 0xffff;
                            v4 = v5; v5 = v6;
                            v6 = pixels2[p6] & 0xffff;
                            v7 = v8; v8 = v9;
                            v9 = pixels2[p9] & 0xffff;
                            int sum = k1 * v1 + k2 * v2 + k3 * v3
                                    + k4 * v4 + k5 * v5 + k6 * v6
                                    + k7 * v7 + k8 * v8 + k9 * v9;
                            sum = (sum + scale / 2) / scale;   //scale/2 for rounding
                            if (sum > 65535) sum = 65535;
                            if (sum < 0) sum = 0;
                            _pixels[p] = (short)sum;
                        }
                        break;
                }
            }
        }

        public override void medianFilter()
        {
            throw new NotImplementedException();
        }

        public override void noise(double standardDeviation)
        {
            throw new NotImplementedException();
        }

        public override ImageProcessor crop()
        {
            ImageProcessor ip2 = createProcessor(roiWidth, roiHeight);
            short[] pixels2 = (short[])ip2.getPixels();
            for (int ys = roiY; ys < roiY + roiHeight; ys++)
            {
                int offset1 = (ys - roiY) * roiWidth;
                int offset2 = ys * width + roiX;
                for (int xs = 0; xs < roiWidth; xs++)
                    pixels2[offset1++] = _pixels[offset2++];
            }

            return ip2;
        }

        /// <summary>
        /// Transforms the pixel data using a 65536 entry lookup table.
        /// </summary>
        /// <param name="lut"></param>
        /// <exception cref="ArgumentException"></exception>
        public override void applyTable(int[] lut)
        {
            if (lut.Length != 65536)
                throw new ArgumentException("lut.length!=65536");
            int lineStart, lineEnd, v;
            for (int y = roiY; y < (roiY + roiHeight); y++)
            {
                lineStart = y * width + roiX;
                lineEnd = lineStart + roiWidth;
                for (int i = lineEnd; --i >= lineStart;)
                {
                    v = lut[_pixels[i] & 0xffff];
                    _pixels[i] = (short)v;
                }
            }

            findMinAndMax();
        }

        protected override void process(int op, double value)
        {
            int v1, v2;
            double range = getMax() - getMin();
            //boolean resetMinMax = roiWidth==width && roiHeight==height && !(op==FILL);
            int offset = isSigned16Bit() ? 32768 : 0;
            int min2 = (int)getMin() - offset;
            int max2 = (int)getMax() - offset;
            int fgColor2 = fgColor - offset;
            int intValue = (int)value;

            for (int y = roiY; y < (roiY + roiHeight); y++)
            {
                int i = y * width + roiX;
                for (int x = roiX; x < (roiX + roiWidth); x++)
                {
                    v1 = (_pixels[i] & 0xffff) - offset;
                    switch (op)
                    {
                        case INVERT:
                            v2 = max2 - (v1 - min2);
                            //v2 = 65535 - (v1+offset);
                            break;
                        case FILL:
                            v2 = fgColor2;
                            break;
                        case SET:
                            v2 = intValue;
                            break;
                        case ADD:
                            v2 = v1 + intValue;
                            break;
                        case MULT:
                            v2 = (int)Math.Round(v1 * value);
                            break;
                        case AND:
                            v2 = v1 & intValue;
                            break;
                        case OR:
                            v2 = v1 | intValue;
                            break;
                        case XOR:
                            v2 = v1 ^ intValue;
                            break;
                        case GAMMA:
                            if (range <= 0.0 || v1 == min2)
                                v2 = v1;
                            else
                                v2 = (int)(Math.Exp(value * Math.Log((v1 - min2) / range)) * range + min2);
                            break;
                        case LOG:
                            if (v1 <= 0)
                                v2 = 0;
                            else
                                v2 = (int)(Math.Log(v1) * (max2 / Math.Log(max2)));
                            break;
                        case EXP:
                            v2 = (int)(Math.Exp(v1 * (Math.Log(max2) / max2)));
                            break;
                        case SQR:
                            double d1 = v1;
                            v2 = (int)(d1 * d1);
                            break;
                        case SQRT:
                            v2 = (int)Math.Sqrt(v1);
                            break;
                        case ABS:
                            v2 = (int)Math.Abs(v1);
                            break;
                        case MINIMUM:
                            if (v1 < value)
                                v2 = intValue;
                            else
                                v2 = v1;
                            break;
                        case MAXIMUM:
                            if (v1 > value)
                                v2 = intValue;
                            else
                                v2 = v1;
                            break;
                        default:
                            v2 = v1;
                            break;
                    }

                    v2 += offset;
                    if (v2 < 0)
                        v2 = 0;
                    if (v2 > 65535)
                        v2 = 65535;
                    _pixels[i++] = (short)v2;
                }
            }
        }

        /** Fills the current rectangular ROI. */
        public override void fill()
        {
            process(FILL, 0.0);
        }

        /** Fills pixels that are within roi and part of the mask.
            Does nothing if the mask is not the same as the ROI. */
        public override void fill(ImageProcessor mask)
        {
            if (mask == null)
            {
                fill();
                return;
            }

            int roiWidth = this.roiWidth, roiHeight = this.roiHeight;
            int roiX = this.roiX, roiY = this.roiY;
            if (mask.getWidth() != roiWidth || mask.getHeight() != roiHeight)
                return;
            byte[] mpixels = (byte[])mask.getPixels();
            for (int y = roiY, my = 0; y < (roiY + roiHeight); y++, my++)
            {
                int i = y * width + roiX;
                int mi = my * roiWidth;
                for (int x = roiX; x < (roiX + roiWidth); x++)
                {
                    if (mpixels[mi++] != 0)
                        _pixels[i] = (short)fgColor;
                    i++;
                }
            }
        }

        int[] getHistogram(ImageProcessor mask)
        {
            if (mask.getWidth() != roiWidth || mask.getHeight() != roiHeight)
                throw new ArgumentException(maskSizeError(mask));
            int roiX = this.roiX, roiY = this.roiY;
            //不确定本地参数为什么要重新赋值
            // int roiWidth = this.roiWidth, roiHeight = this.roiHeight;
            byte[] mpixels = (byte[])mask.getPixels();
            int[] histogram = new int[65536];
            for (int y = roiY, my = 0; y < (roiY + roiHeight); y++, my++)
            {
                int i = y * width + roiX;
                int mi = my * roiWidth;
                for (int x = roiX; x < (roiX + roiWidth); x++)
                {
                    if (mpixels[mi++] != 0)
                        histogram[_pixels[i] & 0xffff]++;
                    i++;
                }
            }

            return histogram;
        }

        /** Creates a histogram of length maxof(max+1,256). For small 
            images or selections, computations using these histograms 
            are faster compared to 65536 element histograms. */
        public int[] getHistogram2()
        {
            if (mask != null)
                return getHistogram2(mask);
            int roiX = this.roiX, roiY = this.roiY;
            int roiWidth = this.roiWidth, roiHeight = this.roiHeight;
            int max = 0;
            int value;
            for (int y = roiY; y < (roiY + roiHeight); y++)
            {
                int index = y * width + roiX;
                for (int i = 0; i < roiWidth; i++)
                {
                    value = _pixels[index++] & 0xffff;
                    if (value > max)
                        max = value;
                }
            }

            int size = max + 1;
            if (size < 256) size = 256;
            int[] histogram = new int[size];
            for (int y = roiY; y < (roiY + roiHeight); y++)
            {
                int index = y * width + roiX;
                for (int i = 0; i < roiWidth; i++)
                    histogram[_pixels[index++] & 0xffff]++;
            }

            return histogram;
        }

        private int[] getHistogram2(ImageProcessor mask)
        {
            if (mask.getWidth() != roiWidth || mask.getHeight() != roiHeight)
                throw new ArgumentException(maskSizeError(mask));
            int roiX = this.roiX, roiY = this.roiY;
            //不确定本地参数为什么要重新赋值
            // int roiWidth = this.roiWidth, roiHeight = this.roiHeight;
            byte[] mpixels = (byte[])mask.getPixels();
            int max = 0;
            int value;
            for (int y = roiY; y < (roiY + roiHeight); y++)
            {
                int index = y * width + roiX;
                for (int i = 0; i < roiWidth; i++)
                {
                    value = _pixels[index++] & 0xffff;
                    if (value > max)
                        max = value;
                }
            }

            int size = max + 1;
            if (size < 256) size = 256;
            int[] histogram = new int[size];
            for (int y = roiY, my = 0; y < (roiY + roiHeight); y++, my++)
            {
                int index = y * width + roiX;
                int mi = my * roiWidth;
                for (int i = 0; i < roiWidth; i++)
                {
                    if (mpixels[mi++] != 0)
                        histogram[_pixels[index] & 0xffff]++;
                    index++;
                }
            }

            return histogram;
        }

        void resetPixels(object pixels)
        {
            if (pixels == null)
            {
                if (img != null)
                {
                    img.Dispose();
                    img = null;
                }
            }
        }

        /** Returns 'true' if this is a signed 16-bit image. */
        public bool isSigned16Bit()
        {
            return cTable != null && cTable[0] == -32768f && cTable[1] == -32767f;
        }
    }
}
